;(function ($) {
    // Пространство имен всех событий.
    var eventNamespace = 'waitForImages';

    // Свойства CSS, которые содержат ссылки на изображения.
    $.waitForImages = {
        hasImageProperties: [
            'backgroundImage',
            'listStyleImage',
            'borderImage',
            'borderCornerImage',
            'cursor'
        ],
        hasImageAttributes: ['srcset']
    };

    // Пользовательский селектор для поиска элементов img с действительным
    // атрибутом src, которые еще не загружены.
    $.expr[':'].uncached = function (obj) {
        // Убедитесь, что мы имеем дело с элементом 
        // img с допустимым атрибутом src.
        if (!$(obj).is('img[src][src!=""]')) {
            return false;
        }

        // Свойство `complete` Firefox всегда будет `true`, даже если 
        // изображение не было загружено. Такой способ работает в Firefox.
        var img = new Image();
        img.src = obj.src;
        return !img.complete;
    };

    $.fn.waitForImages = function () {

        var allImgsLength = 0;
        var allImgsLoaded = 0;
        var deferred = $.Deferred();

        var finishedCallback;
        var eachCallback;
        var waitForAll;

        // Обработать объект опций (если он передан).
        if ($.isPlainObject(arguments[0])) {

            waitForAll = arguments[0].waitForAll;
            eachCallback = arguments[0].each;
            finishedCallback = arguments[0].finished;

        } else {

            // Обработать, если используется отложенный объект
            // и передан только один параметр.
            if (arguments.length === 1 && $.type(arguments[0]) === 'boolean') {
                waitForAll = arguments[0];
            } else {
                finishedCallback = arguments[0];
                eachCallback = arguments[1];
                waitForAll = arguments[2];
            }

        }

        // Обработка отсутствующих обратных вызовов.
        finishedCallback = finishedCallback || $.noop;
        eachCallback = eachCallback || $.noop;

        // Преобразование waitForAll в логическое значение Boolean.
        waitForAll = !! waitForAll;

        // Убедитесь, что обратные вызовы являются функциями.
        if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) {
            throw new TypeError('An invalid callback was supplied.');
        }

        this.each(function () {
            // Создайте список всех изображений, в зависимости 
            // от того, какие изображения будут рассматриваться.
            var obj = $(this);
            var allImgs = [];
            // Свойства CSS, которые могут содержать изображение.
            var hasImgProperties = $.waitForImages.hasImageProperties || [];
            // Атрибуты элемента, которые могут содержать изображение.
            var hasImageAttributes = $.waitForImages.hasImageAttributes || [];
            // Соответствовать ссылкам `url()`.
            // Спецификация: http://www.w3.org/TR/CSS2/syndata.html#value-def-uri
            var matchUrl = /url\(\s*(['"]?)(.*?)\1\s*\)/g;

            if (waitForAll) {

                // Получите все элементы (включая оригинал), так как любой из них может иметь фоновое изображение.
                obj.find('*').addBack().each(function () {
                    var element = $(this);

                    // Если элемент, добавьте его. Но продолжайте повторять, 
                    //	если у него также есть фоновое изображение.
                    if (element.is('img:uncached')) {
                        allImgs.push({
                            src: element.attr('src'),
                            element: element[0]
                        });
                    }

                    $.each(hasImgProperties, function (i, property) {
                        var propertyValue = element.css(property);
                        var match;

                        // Если он не содержит этого свойства, пропустите.
                        if (!propertyValue) {
                            return true;
                        }

                        // Получить все URL () этого элемента.
                        while (match = matchUrl.exec(propertyValue)) {
                            allImgs.push({
                                src: match[2],
                                element: element[0]
                            });
                        }
                    });

                    $.each(hasImageAttributes, function (i, attribute) {
                        var attributeValue = element.attr(attribute);
                        var attributeValues;

                        // Если он не содержит этого свойства, пропустите.
                        if (!attributeValue) {
                            return true;
                        }

                        // Проверьте наличие нескольких изображений, 
                        // разделенных запятыми
                        attributeValues = attributeValue.split(',');

                        $.each(attributeValues, function(i, value) {
                            // Обрезать значение и получить строку перед
                            // первым пробелом (для использования с srcset).
                            value = $.trim(value).split(' ')[0];
                            allImgs.push({
                                src: value,
                                element: element[0]
                            });
                        });
                    });
                });
            } else {
                // Только для изображений задача проще.
                obj.find('img:uncached')
                    .each(function () {
                    allImgs.push({
                        src: this.src,
                        element: this
                    });
                });
            }

            allImgsLength = allImgs.length;
            allImgsLoaded = 0;

            // Если изображения не найдены, не беспокойтесь.
            if (allImgsLength === 0) {
                finishedCallback.call(obj[0]);
                deferred.resolveWith(obj[0]);
            }

            $.each(allImgs, function (i, img) {

                var image = new Image();
                var events =
                  'load.' + eventNamespace + ' error.' + eventNamespace;

                // Обработайте загрузку изображения и ошибку с помощью 
                // одного и того же обратного вызова.
                $(image).one(events, function me (event) {
                    // Если при загрузке изображения произошла ошибка, 
                    // установите соответствующий третий аргумент.
                    var eachArguments = [
                        allImgsLoaded,
                        allImgsLength,
                        event.type == 'load'
                    ];
                    allImgsLoaded++;

                    eachCallback.apply(img.element, eachArguments);
                    deferred.notifyWith(img.element, eachArguments);

                    // Отмените привязку прослушивателей событий. Я использую 
                    // это в дополнение к `one`, так как одно из этих событий 
                    // не будет вызвано (будет вызвано либо 'load', либо 'error').
                    $(this).off(events, me);

                    if (allImgsLoaded == allImgsLength) {
                        finishedCallback.call(obj[0]);
                        deferred.resolveWith(obj[0]);
                        return false;
                    }

                });

                image.src = img.src;
            });
        });

        return deferred.promise();

    };
}(jQuery));
